<?php
namespace GrouppaGroup;
require_once 'MessageFromServer.php';
require_once 'MessageFromApp.php';
require_once 'MCrypt.php';
require_once 'ErrorMessage.php';
require_once 'Settings.php';

class ApplicationTCPConnection
{
    private $userConnected = false;
    /**
     * an array of responses to client's messages
     * @var array
     */
    public static $responses = array();

    /**
     * sends a response message to the client
     * @param MessageFromServer $message message to be sent
     */
    public static function sendMessage(MessageFromServer $message)
    {
        //CHECK: puts a response in the memory ,to be sent later
        self::$responses[] = $message;
    }

    /**
     * sends all the accumulated responses back to the application
     * compresses them and encrypts properly
     * This function should be called only once in the end to send all pending messages
     */
    public function sendAllResponses()
    {
        //CHECK: send all responses back to the client
        $noResponses = count(self::$responses);

        $responsesJSON = '';
        if ($noResponses != 0) {
            //CHECK: convert responses to JSON string
            foreach (self::$responses as $response) {
                if ($responsesJSON)
                    $responsesJSON .= ',';
                $responsesJSON .= $response->getJSON();
            }
            $responsesJSON = '[' . $responsesJSON . ']';
            //CHECK: compress and encrypt responses
            $encrypted = self::compressEncryptMessages($responsesJSON);

            if (!array_key_exists('DEBUG', $GLOBALS))
                header('Content-Length: ' . strlen($encrypted));
            echo $encrypted;
        } else {
            if (!array_key_exists('DEBUG', $GLOBALS))
                header('Content-Length: 0');
        }
        self::$responses = array();
    }

    /**
     * called when a message was received from some client
     * @param MessageFromApp $message
     */
    public function onMessageReceived(MessageFromApp $message)
    {
        //CHECK: send the received message to a proper controller
        if ($message->toController != '') {
            //load controller
            $controller = self::loadController($message->toController);
            //CHECK: call method
            $controller->receiveMessage($message);
        }
    }

    private static $loadedControllers = array();
    /**
     * Loads (and constructs if necessary) a controller class by it's name
     * @param  string     $controllerName name of the controller, for example 'Chat' for ChatController
     * @return Controller a subclass of controller by it's name
     */
    public static function loadController($controllerName)
    {

        //CHECK: return old instance if it was created earlier
        if (isset(self::$loadedControllers[$controllerName]))
            return self::$loadedControllers[$controllerName];

        //CHECK: load a class by it's name if it isn't loaded already
        if (!class_exists($controllerName)) {
            $filename = $controllerName . 'Controller.php';
            require_once $filename;
        }
        $controllerName = '\\GrouppaGroup\\' . $controllerName . 'Controller';

        //CHECK: create a new instance of it if it doesn't exist already
        self::$loadedControllers[$controllerName] = new $controllerName();

        return self::$loadedControllers[$controllerName];
    }

    /**
     * compresses and encodes the text from messages
     * output is done directly to php://output
     * @param  string $messages text to encode and compress
     * @return string array of bytes with encrypted and compressed messages
     */
    public static function compressEncryptMessages($messages)
    {
        //CHECKED: compression and encryption of messages
        return MCrypt::encryptToBytes(self::compressMessage($messages));
    }

    /**
     * decrypts and decompresses a message from android
     * @param  string $data compressed (android deflate) and encrypted data
     * @return string decrypted and decompressed data
     */
    public static function decompressDecryptMessages($data)
    {
        //CHECKED:  decrypt and decompress
        return self::decompressMessage(Mcrypt::decryptFromBytes($data));
    }

    /**
     * Compressing message
     * @param  string $message message to compress
     * @return string compressed message
     */
    public static function compressMessage($message)
    {
        if (strlen($message) == 0)
            return '';
        //CHECKED: compress the message
        $compressed = gzdeflate($message, COMPRESSION_LEVEL);
        $checksum = mhash(MHASH_ADLER32, $message);
        //some additional things specially for android
        return chr(120) . chr(-100) . $compressed . $checksum;
    }

    /**
     * Decompressing message from android compression
     * (will not work with PHP gzinflate)
     * @param  string $compressedMessage compressed message
     * @return string
     */
    public static function decompressMessage($compressedMessage)
    {
        if (strlen($compressedMessage) == 0)
            return '';
        //CHECKED:  decompress a message from android deflate
        return gzinflate(substr($compressedMessage, 2, -4));
    }

    /**
     * Loads all messages from compressed and encrypted data
     * @param string $data compressed and encrypted data
     */
    public function loadMessages($data)
    {
        //CHECK:  load all messages
        $lastMessageId = -1;
        try {
            $data = self::decompressDecryptMessages($data);
            $data = json_decode($data);
            //CHECK:  create a class messagefromapp for each of messages
            foreach ($data as $message) {
                try {
                    $mDecoded = MessageFromApp::createFromJSON($message);
                    $lastMessageId = $mDecoded->id;
                    $this->onMessageReceived($mDecoded);
                } catch (\Exception $ex) {
                    //CHECK: send simple error message in return
                    $errorMessage = new ErrorMessage("ApplicationTCPConnect::loadMessages: error during loading message number $lastMessageId : " .
                        $ex);
                    $this->sendMessage($errorMessage);
                }
            }
        } catch (\Exception $ex) {
            //CHECK: send simple error message in return
            $errorMessage = new ErrorMessage('ApplicationTCPConnect::loadMessages: error during loading messages : ' .
                $ex);
            $this->sendMessage($errorMessage);
        }
    }

}
